
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

/**
 * Semantic Parser.
 * Class responsible for the semantic analysis.
 *
 */
public class SemanticParser {

	private Hashtable<Integer, SimpleNode> integerHash;
	private Hashtable<String, SimpleNode> stringHash;
	public GraphBuilder graphBuilder = null;

	/**
	 * Constructor for the SemanticParser class.
	 * 
	 * @param root
	 *            Root of the abstract tree
	 * @throws Exception 
	 */
	public SemanticParser(SimpleNode root) throws Exception {
		integerHash = new Hashtable<Integer, SimpleNode>();
		stringHash = new Hashtable<String, SimpleNode>();
		for (Node c : root.children) {
			SimpleNode child = (SimpleNode) c;
			if(integerHash.get(child.i85Id) != null)
				throw new Exception("Error on line " + child.i85StartLine + ":\n Node id " + child.i85Id + " is already in use\n");
			if(stringHash.get(child.i85Name) != null)
				throw new Exception("Error on line " + child.i85StartLine + ":\n Node name " + child.i85Name + " is already in use\n");
			integerHash.put(child.i85Id, child);
			stringHash.put(child.i85Name, child);
		}
	}

	/**
	 * Builds the graph representation of the circuit from the abstract tree.
	 * 
	 * @return TRUE if the graph is linked and contains no cycles, FALSE
	 *         otherwise
	 */
	public boolean buildGraph() {
		graphBuilder = new GraphBuilder(stringHash, integerHash);
		return graphBuilder.buildGraph();
	}

	/**
	 * Check if all referenced nodes exist
	 * 
	 * @return TRUE if no semantic error are found, FALSE otherwise
	 */
	public boolean checkSemantic() {
		Enumeration<Integer> e = integerHash.keys();
		boolean valid = true;

		while (e.hasMoreElements()) {
			SimpleNode node = integerHash.get(e.nextElement());
			switch (node.i85Gate) {
			case INPT:
				valid = checkInputNodes(node) ? valid : false;
				break;
			case FROM:
				valid = checkFanOutValues(node) ? valid : false;
				break;
			default:
				valid = checkFanInValues(node) ? valid : false;
				break;
			}
		}
		return valid;
	}

	/**
	 * Check if fan in value and list length match
	 * 
	 * @return TRUE if no semantic error are found, FALSE otherwise
	 */
	public boolean checkFanInValues(SimpleNode node) {
		// Check fanin values
		// If not (INPT || FROM || BUFF || NOT) fanin value must be >= 2
		List<Integer> fanins = node.i85FaninList;
		if (fanins.size() >= 2 && (node.i85Gate != NetlistParser.GateType.BUFF && node.i85Gate != NetlistParser.GateType.NOT)) {
			if (fanins.size() == node.i85FaninValue) {
				for (Integer fn : fanins) {
					SimpleNode from = integerHash.get(fn);

					// Invalid node reference error
					if (from == null) {
						System.out.print("Error on line " + node.i85StartLine + ": ");
						System.out.print(fn);
						System.out.println(" is an invalid node address");
						return false;
					}
				}
			} else {
				// Fanin node quantity is not the same as the number
				// of fanin ids given
				System.out.println("Error on line " + node.i85StartLine + ": insufficient node addresses in fanin line;");
				System.out.print("expected ");
				System.out.print(node.i85FaninValue);
				System.out.print(", have ");
				System.out.println(fanins.size());
				return false;
			}
		} else if (fanins.size() == 1 && (node.i85Gate == NetlistParser.GateType.BUFF || node.i85Gate == NetlistParser.GateType.NOT)) {
			if (fanins.size() == node.i85FaninValue) {
				SimpleNode from = integerHash.get(fanins.get(0));

				// Invalid node reference error
				if (from == null) {
					System.out.print("Error on line " + node.i85StartLine + ": ");
					System.out.print(from);
					System.out.println(" is an invalid node address");
					return false;
				}
			} else {
				// Fanin node quantity is not the same as the number
				// of fanin ids given
				System.out.println("Error on line " + node.i85StartLine + ": insufficient node addresses in fanin line;");
				System.out.print("expected ");
				System.out.print(node.i85FaninValue);
				System.out.print(", have ");
				System.out.println(fanins.size());
				return false;
			}
		} else {
			// Invalid fanin value error
			System.out.println("Error on line " + node.i85StartLine + ": fanin value is invalid");
			return false;
		}

		return true;
	}

	/**
	 * Check if fan out names exist and are valid identifiers
	 * 
	 * @return TRUE if no semantic error are found, FALSE otherwise
	 */
	public boolean checkFanOutValues(SimpleNode node) {
		SimpleNode from = stringHash.get(node.i85FanoutName);

		// Invalid node name error
		if (from == null) {
			System.out.print("Error on line " + node.i85StartLine + ": ");
			System.out.print(node.i85FanoutName);
			System.out.println(" is an invalid node name");
			return false;
		} else
			return true;
	}

	/**
	 * Checks if the fanin of an input node is 0
	 * @param node
	 * 			node to test
	 * @return
	 * 		true if ok, false otherwise
	 */
	public boolean checkInputNodes(SimpleNode node) {
		// Input nodes with fanin error
		if (node.i85FaninValue != 0) {
			System.out.println("Error on line " + node.i85StartLine + ": INPT shouldn't have fanin");
			return false;
		} else
			return true;
	}
}
